<!DOCTYPE html>
<html>

<head>
    <title>mfoElement Example</title>
</head>

<body>
    <script>
        const hasOwn = {}.hasOwnProperty;
        const isPlainObject = function (obj) {
            var key;
            if (!obj || typeof obj !== "object" || obj.nodeType) {
                return false;
            }
            try {
                if (
                    obj.constructor &&
                    !hasOwn.call(obj, "constructor") &&
                    !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")
                ) {
                    return false;
                }
            } catch (e) {
                return false;
            }
            for (key in obj) {
            }
            return key === undefined || hasOwn.call(obj, key);
        };
        class Ref {
            constructor(ops) {
                const that = this;
                if (isPlainObject(ops)) {
                    Object.keys(ops).forEach(val => {
                        if (
                            ops[val].type &&
                            ops[val].default &&
                            (typeof ops[val].type === 'function' ? typeof ops[val].type() !== typeof ops[val].default() : ops[val].type !== typeof ops[val].default())
                        ) {
                            console.error(typeof ops[val].type(), typeof ops[val].default());
                            return;
                        }
                        this.options = ops;
                        this['_' + val] = this.options[val].default();
                        Object.defineProperty(this, [val], {
                            configurable: true,
                            enumerable: true,
                            get() {
                                return this['_' + val]
                            },
                            set(nval) {
                                this['_' + val] = nval;
                                that.updatedEventHandle()
                            }
                        })
                    })
                }
                return this;
            }
            updatedEventHandle = () => { };
            updated(callback) {
                const that = this;
                that.updatedEventHandle = () => {
                    typeof callback === "function" && callback();
                };
                //that.updatedEventHandle();
                return {
                    trigger() {
                        that.updatedEventHandle();
                    },
                };
            }
        }
        const query = (selector) => {
            return document.querySelector(selector)
        }
        class Element {
            #options = {};
            constructor(ops) {
                this.#options = ops
                return this
            }
            get options() {
                return this.#options
            }
            set options(val) {
                this.#options = val;
            }
            tmpl(data, template) {
                let html = template.innerHTML;
                Object.keys(data).forEach(key => {
                    const regx = new RegExp(`{{\\\s*${key}\\\s*}}`, 'gim')
                    html = html.replace(regx, data[key]);
                })
                template.innerHTML = html;
                const elem = document.createElement('div');
                elem.append(template.content.cloneNode(true));
                const findNode = (elem, filter) => {
                    elem.childNodes.forEach(node => {
                        filter(node);
                        if (node.childNodes.length > 0) findNode(node, filter)
                    })
                }
                findNode(elem, (node) => {
                    node.attributes && [...node.attributes].forEach(attr => {
                        if (/@/.test(attr.name)) {
                            node[attr.name.replace('@', "on")] = function (e) {
                                this.options.methods[attr.value].call(this, e)
                            };
                            node.removeAttribute(attr.name)
                        }
                    })
                    return node;
                })
                console.log(elem.innerHTML)
                return elem;
            }
            render(id) {
                const that = this;
                const temp = this.options.template;
                const data = this.options.data();
                Object.keys(data).forEach(key => {
                    const val = data[key]
                    data[key] = {
                        type: typeof val,
                        default() {
                            return val
                        }
                    }
                })
                const refData = new Ref(data)
                this.elem = this.tmpl(refData, temp)
                refData.updated(() => {
                    that.elem = that.tmpl(refData, temp)
                })
                console.log(this.elem.cloneNode(true))
                query(id).append(this.elem.cloneNode(true))
            }
        }
        const createComponent = (ops) => {
            return new Element(ops)
        }
    </script>
    <template id="header">
        <div class="app">
            test {{a}}
            <button @click="buttonHandle">button</button>
        </div>
    </template>
    <div id="app"></div>
    <script>
        const app = createComponent({
            data() {
                return {
                    a: 1
                }
            },
            template: query('#header'),
            methods: {
                buttonHandle() {
                    console.log('button')
                }
            }
        });
        app.render("#app")
    </script>
</body>

</html>